/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */


#include <arch/scr.h>
#include <core/dprintf.h>
#include <core/mm.h>
#include <core/schedule.h>
#include <core/task.h>
#include <caps/capability.h>
#include <caps/cnode.h>
#include <caps/task.h>
#include <uapi/errors.h>
#include <uapi/ipcops.h>

#define UID_TEST

static void cap_task_set_obj(capability_t *cap, tcb_t *tcb)
{
    cap_set_long_info(cap, (unsigned long)tcb);
}

void cap_task_init(capability_t *cap, tcb_t *tcb)
{
    assert(tcb != 0);

    tcb_get(tcb);
    cap_basic_init(cap, CAP_THREAD);
    cap_task_set_obj(cap, tcb);
}

static int handle_clone(capability_t *target, unsigned long cptr, unsigned long id, unsigned long src_cptr)
{
    tcb_t *src;
    if (src_cptr) {
        src = cap_task_get_obj(cnode_cptr_lookup(task_get_cnode(current()), src_cptr, sizeof(unsigned long) * 8));
    } else {
        src = current();
    }

    if (cptr != 0) {
        capability_t *cap = cnode_cptr_lookup(task_get_cnode(current()), cptr, sizeof(unsigned long) * 8);
        if ((cap == NULL) || (cap_get_type(cap) != CAP_THREAD)) {
            return -ENOCAP;
        }

        src = cap_task_get_obj(cap);
    }

    task_clone(cap_task_get_obj(target), src, id);
    return ESUCCESS;
}

static int handle_config(capability_t *cap, unsigned int msglen, unsigned long entry, unsigned long ipcbuffer)
{
    tcb_t *task = cap_task_get_obj(cap);

    unsigned long *args     = ipcbuffer_get_msgs(task_get_ipcbuffer(current()));
    unsigned long  stack    = args[2];
    unsigned long  stacklen = args[3];
    const char    *name     = (const char *)args[8];
    unsigned int   cpu_set  = args[9];
    unsigned int   sched_policy  = args[10];

    unsigned long maxpriority = task_get_maxpriority(current());
    unsigned long priority    = (args[4] < maxpriority) ? maxpriority : args[4];

    unsigned int timeslice = args[5];

    if ((msglen < 5) || (ipcbuffer == 0) || (stack == 0) || (stacklen < CONFIG_PAGE_SIZE) || (entry == 0)) {
        return -EINVALID;
    }

    /* PRQA S 0326 1 */
    task_set_stack(task, (void *)stack, (void *)ipcbuffer, stacklen);
    if (entry == 1) {
        task_set_entry(task, CONFIG_RS_VBASE);
    } else {
        task_set_entry(task, entry);
    }

    if (priority >= CONFIG_MAX_PRIORITY) {
        priority = CONFIG_MAX_PRIORITY - 1;
    }
    task_set_sched_policy(task, sched_policy);
    task_set_priority(task, priority);
    task_set_orig_priority(task,priority);
    task_set_maxpriority(task, maxpriority);
    task_set_timeslice(task, timeslice);
    task_set_affinity(task, 0);
    task_set_domain(task, 0);
    task->cpu_set = cpu_set;
    if(sched_policy == SCHED_SPORADIC) {
        sporadic_param_t *param = (sporadic_param_t*)args[6];
        task->ss_low_priority  = param->ss_low_priority;
        task->ss_repl_period = param->ss_repl_period;
        task->ss_init_budget   = param->ss_init_budget;
        task->curr_budget = task->ss_init_budget;
        task->activation_time = timer_current().time;
    }

#ifdef CONFIG_ENABLE_SMP
    if (task->cpu_set == AFFINITY_CPU3) {
        task->affinity = 3;
    } else if (task->cpu_set == AFFINITY_CPU2) {
        task->affinity = 2;
    } else if (task->cpu_set == AFFINITY_CPU1) {
        task->affinity = 1;
    } else if (task->cpu_set == AFFINITY_CPU0) {
        task->affinity = 0;
    } else {
        task->affinity = 0;
    }
#else
    task->affinity = 0;
#endif

    unsigned long elf = args[7];
    if (!elf) {
        task_set_elf_cptr(task, 0);
    } else if (elf == 1UL) {
        task_set_elf_cptr(task, task->cptr);
    } else {
        task_set_elf_cptr(task, elf);
    }

    task_set_name(task, name);
    task_set_retcode(task, args[6]);
    if (msglen > 10) {
        task_set_reg_param(task, args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17]);
    }

    if (entry == 1) {
        task_set_state(task, TASK_IDLE);
    } else {
        task_set_state(task, TASK_READY);
        schedule_attach(task);
    }

    return ESUCCESS;
}

#define JOIN_DATA_SIZE 96
uint64_t   join_data[JOIN_DATA_SIZE] = { 0 };
const int  join_data_size            = JOIN_DATA_SIZE;
static int handle_join(capability_t *cap, unsigned long _rtn)
{
    if (!cap) {
        return -ECANCELED;
    }

    tcb_t   *target = cap_task_get_obj(cap);
    uint32_t state  = task_get_state(target);

    if (!target) {
        return ECANCELED;
    }
    if (target == current()) {
        return 36;
    }
    if (target->detached) {
        return EINVAL;
    }
    if (target->owner != NULL) {
        return EOCCUPIED;
    }
    if (state & 0xF000) {
        if ((state == TASK_EXIT_ZOMBIE) && _rtn) {
            int i, flag = 1;
            for (i = 0; i < JOIN_DATA_SIZE; i += 3) {
                if (join_data[i]) {
                    join_data[i + 2]++;
                    if (join_data[i + 2] >= JOIN_DATA_SIZE) {
                        join_data[i]     = 0;
                        join_data[i + 1] = 0;
                        join_data[i + 2] = 0;
                    }

                    if ((uint64_t)target == join_data[i]) {
                        /* PRQA S 0326 1 */
                        *(void **)_rtn   = (void *)join_data[i + 1];
                        join_data[i]     = 0;
                        join_data[i + 1] = 0;
                        join_data[i + 2] = 0;
                        flag             = 0;
                    }
                }
            }
            if (flag != 0) {
                return EINVALID;
            }
            return ESUCCESS;
        } else {
            return EINVALID;
        }
    }
    if (_rtn) {
        target->rtn = (void **)_rtn;
    }

    target->owner = current();

    tcb_get(current());
    schedule_detach(current());
    task_set_state(current(), TASK_BLOCKED);

    return ESUCCESS;
}

static int handle_detach(capability_t *cap)
{
    if (!cap) {
        return -ECANCELED;
    }

    tcb_t *target = cap_task_get_obj(cap);

    if (target->detached) {
        return EINVAL;
    }

    if (target->rtn != NULL) {
        return ESUCCESS;
    }

    target->detached = 1;

    return ESUCCESS;
}

static int handle_sbrk(capability_t *cap, unsigned long a0)
{
    unsigned long increment = a0;
    unsigned long retval;

    current()->mm.brk += increment;
    retval = current()->mm.brk;

    task_set_retcode(current(), retval);
    return ESUCCESS;
}

static int handle_brk(capability_t *cap, unsigned long a0)
{
    unsigned long addr = a0;
    unsigned long retval;
    if (addr > current()->mm.end_data) {
        current()->mm.brk = addr;
        retval            = 0;
    } else {
        retval = ERR_TO_RETCODE(-1);
    }
    task_set_retcode(current(), retval);
    return ESUCCESS;
}

static int handle_get_mmap_heap_addr(capability_t *cap, unsigned long a0, unsigned long a1)
{
    uint64_t des_addr = a0;
    uint64_t len      = a1;

    uint64_t res = get_user_heap_addr(&current()->mm, des_addr, len);
    task_set_retcode(current(), res);
    return ESUCCESS;
}

static int handle_info_memory(capability_t *cap, unsigned long a0)
{
    uint64_t va = a0;
    check_va_attribute(current()->addrspace.pg, va);
    task_set_retcode(current(), 0);
    return ESUCCESS;
}

static int handle_smp_core_state(capability_t *cap)
{
    task_set_retcode(current(), 0);
    return ESUCCESS;
}

static int handle_thread_param(capability_t *cap, unsigned long a0, unsigned long a1, unsigned long a2)
{
    if (a0 == 0UL) {
        if (!a1) {
            task_set_retcode(current(), current()->cancelstate);
        } else {
            current()->cancelstate = a2;
            task_set_retcode(current(), 0);
        }
    } else if (a0 == 1UL) {
        if (!a1) {
            task_set_retcode(current(), current()->canceltype);
        } else {
            current()->canceltype = a2;
            task_set_retcode(current(), 0);
        }
    } else if (a0 == 2UL) {
        if (!a1) {
            task_set_retcode(current(), current()->canceled);
        }
    } else if (a0 == 3UL) {
        if (!a1) {
            task_set_retcode(current(), current()->woken_by_cancel);
        } else {
            current()->woken_by_cancel = a2;
            task_set_retcode(current(), 0);
        }
    } else if (a0 == 4UL) {
        if (!a1) {
            task_set_retcode(current(), current()->condvar_avail);
        }
    } else if (a0 == 5UL) {
        if (a1 == (unsigned long)-1) {
            task_set_retcode(current(), (uint64_t)current()->specific[a2]);
        } else {
            /* PRQA S 0326 1 */
            current()->specific[a1] = (void *)a2;
            task_set_retcode(current(), 0);
        }
    } else if (a0 == 6UL) {
        if (!a1) {
            task_set_retcode(current(), (uint64_t)current()->cleanup);
        } else {
            current()->cleanup = (struct pthread_cleanup_buffer *)a2;
            task_set_retcode(current(), 0);
        }
    } else {
        task_set_retcode(current(), ERR_TO_RETCODE(-1));
    }
    return ESUCCESS;
}

void cap_task_dispatcher(capability_t *cap, unsigned long tag, unsigned long m0, unsigned long m1, unsigned long m2,
                         unsigned long m3, unsigned long m4, unsigned long m5)
{
    switch (msgtag_get_op(tag)) {
        case CAP_THREAD_CLONE:
            task_set_retcode(current(), handle_clone(cap, m0, m1, m2));
            break;

        case CAP_THREAD_CONFIG:
            task_set_retcode(current(), handle_config(cap, msgtag_get_len(tag), m0, m1));
            break;

        case CAP_THREAD_JOIN:
            task_set_retcode(current(), handle_join(cap, m0));
            break;

        case CAP_THREAD_DETACH:
            task_set_retcode(current(), handle_detach(cap));
            break;

        case CAP_THREAD_SBRK:
            task_set_retcode(current(), handle_sbrk(cap, m0));
            break;

        case CAP_THREAD_BRK:
            task_set_retcode(current(), handle_brk(cap, m0));
            break;

        case CAP_THREAD_MM_HEAP_ADDRESS:
            task_set_retcode(current(), handle_get_mmap_heap_addr(cap, m0, m1));
            break;

        case CAP_THREAD_INFO_MEMORY:
            task_set_retcode(current(), handle_info_memory(cap, m0));
            break;

        case CAP_THREAD_CORE_STATE:
            task_set_retcode(current(), handle_smp_core_state(cap));
            break;

        case CAP_THREAD_OPTION:
            task_set_retcode(current(), handle_thread_param(cap, m0, m1, m2));
            break;

        case CAP_THREAD_EXIT: {
            tcb_t           *quit = current();
            schedule_data_t *sd   = current_schedule_data();

            free_mmap_region(quit);

            scr_invalidate_tlb_asid_el1(quit->addrspace.asid);

            addrspace_recycle(&quit->addrspace);
            task_set_state(quit, TASK_EXIT_ZOMBIE);
            if (readyqueue_is_empty(sd, curDomainId) != 0) {
                sd->action = ACTION_RUN_IDLE;
            } else {
                sd->action = ACTION_CHOOSE_NEW;
            }

            repl_clear_by_task(quit);
            break;
        }
        case CAP_THREAD_YIELD:
            task_set_retcode(current(), ESUCCESS);
            schedule_detach(current());
            break;

        case CAP_THREAD_BLOCK:

            break;

        case CAP_THREAD_SOFT_BLOCK: {
            tcb_t *soft_time = NULL;
            soft_time        = current();

            schedule_detach(soft_time);
            task_set_state(soft_time, TASK_BLOCKED);
            break;
        }

        case CAP_THREAD_NANOSLEEP: {
            tcb_t *cur = current();
#ifdef CONFIG_NEW_TIME
            cur->timeslice.readytime.time = timer_current().time + m0 / 1000;
#else
            cur->readytime = timer_current().time + m0 / 1000;
#endif
            task_set_state(cur, TASK_SLEEP);
            task_set_retcode(cur, ESUCCESS);
            schedule_detach(current());
            break;
        }
        case CAP_THREAD_USLEEP: {
            tcb_t *cur = current();

#ifdef CONFIG_KERNEL_MCS
            refill_head(cur->tcbSchedContext)->rTime = timer_current().time + m0;
#else
#ifdef CONFIG_NEW_TIME
            cur->timeslice.readytime.time = timer_current().time + m0;
#else
            cur->readytime = timer_current().time + m0;
#endif
#endif
            task_set_state(cur, TASK_SLEEP);
            task_set_retcode(cur, ESUCCESS);
            schedule_detach(current());
            break;
        }
        case CAP_THREAD_CPU_USAGE: {
            extern unsigned int cpu_usage;
            task_set_retcode(current(), cpu_usage);
            break;
        }

        case CAP_THREAD_SETUID: {
#ifdef UID_TEST
            current()->uid = 0;
#endif
            int   retval;
            uid_t new_uid = (uid_t)m0;
            dprintf("setuid system call => new_uid = %d  \n", new_uid);

            if ((current()->uid == 0U) || (current()->suid == 0U) || (current()->gid == 0U) ||
                (current()->sgid == 0U)) {
                current()->uid  = new_uid;
                current()->euid = new_uid;
                current()->suid = new_uid;
                retval          = 0;
            } else if ((current()->uid == new_uid) || (current()->suid == new_uid)) {
                current()->euid = new_uid;
                retval          = 0;
            } else {
                retval = -1;
            }

            task_set_retcode(current(), retval);
            break;
        }

        case CAP_THREAD_SETEUID: {
#ifdef UID_TEST
            uid_t pre_uid  = current()->uid;
            current()->uid = 0;
#endif
            int   retval;
            uid_t new_euid = (uid_t)m0;
            dprintf("seteuid system call => new_euid = %d  \n", new_euid);

            if ((current()->uid == 0U) || (current()->suid == 0U) || (current()->gid == 0U) ||
                (current()->sgid == 0U)) {
                current()->euid = new_euid;
                retval          = 0;
            } else if ((current()->uid == new_euid) || (current()->suid == new_euid)) {
                current()->euid = new_euid;
                retval          = 0;
            } else {
                retval = -1;
            }

#ifdef UID_TEST
            current()->uid = pre_uid;
#endif

            task_set_retcode(current(), retval);
            break;
        }

        case CAP_THREAD_SETGID: {
#ifdef UID_TEST
            current()->gid = 0;
#endif
            int   retval;
            uid_t new_gid = (uid_t)m0;
            dprintf("setgid system call => new_gid = %d  \n", new_gid);

            if ((current()->uid == 0U) || (current()->suid == 0U) || (current()->gid == 0U) ||
                (current()->sgid == 0U)) {
                current()->gid  = new_gid;
                current()->egid = new_gid;
                current()->sgid = new_gid;
                retval          = 0;
            } else if ((current()->gid == new_gid) || (current()->sgid == new_gid)) {
                current()->egid = new_gid;
                retval          = 0;
            } else {
                retval = -1;
            }

            task_set_retcode(current(), retval);
            break;
        }

        case CAP_THREAD_SETEGID: {
#ifdef UID_TEST
            uid_t pre_uid  = current()->uid;
            current()->uid = 0;
#endif
            int   retval;
            uid_t new_egid = (uid_t)m0;

            if ((current()->uid == 0U) || (current()->suid == 0U) || (current()->gid == 0U) ||
                (current()->sgid == 0U)) {
                current()->egid = new_egid;
                retval          = 0;
            } else if ((current()->gid == new_egid) || (current()->sgid == new_egid)) {
                current()->egid = new_egid;
                retval          = 0;
            } else {
                retval = -1;
            }
#ifdef UID_TEST
            current()->uid = pre_uid;
#endif
            task_set_retcode(current(), retval);
            break;
        }

        case CAP_THREAD_GETUID:
            task_set_retcode(current(), current()->uid);
            break;

        case CAP_THREAD_GETEUID:
            task_set_retcode(current(), current()->euid);
            break;

        case CAP_THREAD_GETGID:
            task_set_retcode(current(), current()->gid);
            break;

        case CAP_THREAD_GETEGID:
            task_set_retcode(current(), current()->egid);
            break;

        case CAP_SET_ELF_HEAP: {
            tcb_t   *task      = current();
            int      map_size  = task->mm.mmap_index;
            uint64_t top       = task->mm.mmap[map_size - 1].vm_end;
            task->mm.start_brk = top;
            task->mm.brk       = top;
            task_set_retcode(task, ESUCCESS);
            break;
        }

        case CAP_THREAD_leave: {
            tcb_t *cur = current();
            if (cur->rtn != NULL) {
                /* PRQA S 0326 1 */
                *(cur->rtn) = (void *)m0;
            }
            if (cur->detached) {
                ;
            } else if (cur->owner != NULL) {
                (void)tcb_put(cur->owner);
                task_set_state(cur->owner, TASK_READY);
                schedule_attach(cur->owner);
            } else {
                extern uint64_t  join_data[];
                extern const int join_data_size;
                int              i;
                for (i = 0; i < join_data_size; i += 3) {
                    if (!join_data[i]) {
                        join_data[i]     = (uint64_t)cur;
                        join_data[i + 1] = m0;
                        join_data[i + 2] = 0;
                        break;
                    }
                }
            }
            task_set_retcode(cur, ESUCCESS);
        } break;
        default:
            task_set_retcode(current(), ERR_TO_RETCODE(-ENOSYS));
            break;
    }
}
